// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
//
// Authors:
//	Peter Bartok	(pbartok@novell.com)
//  Andreia Gaita	(avidigal@novell.com)
//
//

using System.Collections;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using System;

namespace SharpBoardLibrary.Keyboard
{
    public class KeyBoardDevice
    {
        private struct Keyword
        {
            public Keyword(string keyword, int vk)
            {
                this.keyword = keyword;
                this.vk = vk;
            }
            internal string keyword;
            internal int vk;
        }

        #region Local variables
        private static Queue keys = new Queue();
        private static Hashtable keywords;
        #endregion

        static KeyBoardDevice()
        {
            KeyBoardDevice.keywords = new Hashtable();

            keywords.Add("BACKSPACE", (int)Keys.Back);
            keywords.Add("BS", (int)Keys.Back);
            keywords.Add("BKSP", (int)Keys.Back);
            keywords.Add("BREAK", (int)Keys.Cancel);
            keywords.Add("CAPSLOCK", (int)Keys.CapsLock);
            keywords.Add("DELETE", (int)Keys.Delete);
            keywords.Add("DEL", (int)Keys.Delete);
            keywords.Add("DOWN", (int)Keys.Down);
            keywords.Add("END", (int)Keys.End);
            keywords.Add("ENTER", (int)Keys.Enter);
            keywords.Add("~", (int)Keys.Enter);
            keywords.Add("ESC", (int)Keys.Escape);
            keywords.Add("HELP", (int)Keys.Help);
            keywords.Add("HOME", (int)Keys.Home);
            keywords.Add("INSERT", (int)Keys.Insert);
            keywords.Add("INS", (int)Keys.Insert);
            keywords.Add("LEFT", (int)Keys.Left);
            keywords.Add("NUMLOCK", (int)Keys.NumLock);
            keywords.Add("PGDN", (int)Keys.PageDown);
            keywords.Add("PGUP", (int)Keys.PageUp);
            keywords.Add("PRTSC", (int)Keys.PrintScreen);
            keywords.Add("RIGHT", (int)Keys.Right);
            keywords.Add("SCROLLLOCK", (int)Keys.Scroll);
            keywords.Add("TAB", (int)Keys.Tab);
            keywords.Add("UP", (int)Keys.Up);
            keywords.Add("F1", (int)Keys.F1);
            keywords.Add("F2", (int)Keys.F2);
            keywords.Add("F3", (int)Keys.F3);
            keywords.Add("F4", (int)Keys.F4);
            keywords.Add("F5", (int)Keys.F5);
            keywords.Add("F6", (int)Keys.F6);
            keywords.Add("F7", (int)Keys.F7);
            keywords.Add("F8", (int)Keys.F8);
            keywords.Add("F9", (int)Keys.F9);
            keywords.Add("F10", (int)Keys.F10);
            keywords.Add("F11", (int)Keys.F11);
            keywords.Add("F12", (int)Keys.F12);
            keywords.Add("F13", (int)Keys.F13);
            keywords.Add("F14", (int)Keys.F14);
            keywords.Add("F15", (int)Keys.F15);
            keywords.Add("F16", (int)Keys.F16);
            keywords.Add("ADD", (int)Keys.Add);
            keywords.Add("SUBTRACT", (int)Keys.Subtract);
            keywords.Add("MULTIPLY", (int)Keys.Multiply);
            keywords.Add("DIVIDE", (int)Keys.Divide);
            keywords.Add("+", (int)Keys.ShiftKey);
            keywords.Add("^", (int)Keys.ControlKey);
            keywords.Add("%", (int)Keys.Menu);
        }

        #region Private methods

        private KeyBoardDevice()
        {
        }


        private static void AddVKey(int vk, bool down)
        {
            MSG msg = new MSG();
            msg.message = down ? Msg.WM_KEYDOWN : Msg.WM_KEYUP;
            msg.wParam = new IntPtr(vk);
            msg.lParam = IntPtr.Zero;
            keys.Enqueue(msg);
        }

        private static void AddVKey(int vk, int repeat_count)
        {
            MSG msg;

            for (int i = 0; i < repeat_count; i++)
            {
                msg = new MSG();
                msg.message = Msg.WM_KEYDOWN;
                msg.wParam = new IntPtr(vk);
                msg.lParam = (IntPtr)1;
                keys.Enqueue(msg);

                msg = new MSG();
                msg.message = Msg.WM_KEYUP;
                msg.wParam = new IntPtr(vk);
                msg.lParam = IntPtr.Zero;
                keys.Enqueue(msg);

            }
        }

        private static void AddKey(char key, int repeat_count)
        {
            MSG msg;

            for (int i = 0; i < repeat_count; i++)
            {
                msg = new MSG();
                msg.message = Msg.WM_KEYDOWN;
                msg.wParam = new IntPtr(key);
                msg.lParam = IntPtr.Zero;
                keys.Enqueue(msg);

                msg = new MSG();
                msg.message = Msg.WM_KEYUP;
                msg.wParam = new IntPtr(key);
                msg.lParam = IntPtr.Zero;
                keys.Enqueue(msg);
            }
        }

        private static void Parse(string key_string)
        {
            bool isBlock = false;
            bool isVkey = false;
            bool isRepeat = false;
            bool isShift = false;
            bool isCtrl = false;
            bool isAlt = false;

            StringBuilder repeats = new StringBuilder();
            StringBuilder group_string = new StringBuilder();

            int key_len = key_string.Length;
            for (int i = 0; i < key_len; i++)
            {
                switch (key_string[i])
                {
                    case '{':

                        group_string.Remove(0, group_string.Length);
                        repeats.Remove(0, repeats.Length);
                        int start = i + 1;
                        for (; start < key_len && key_string[start] != '}'; start++)
                        {
                            if (Char.IsWhiteSpace(key_string[start]))
                            {
                                if (isRepeat)
                                    throw new ArgumentException("SendKeys string {0} is not valid.", key_string);

                                isRepeat = true;
                                continue;
                            }
                            if (isRepeat)
                            {
                                if (!Char.IsDigit(key_string[start]))
                                    throw new ArgumentException("SendKeys string {0} is not valid.", key_string);

                                repeats.Append(key_string[start]);

                                continue;
                            }

                            group_string.Append(key_string[start]);
                        }
                        if (start == key_len || start == i + 1)
                            throw new ArgumentException("SendKeys string {0} is not valid.", key_string);

                        else if (KeyBoardDevice.keywords.Contains(group_string.ToString().ToUpper()))
                        {
                            isVkey = true;
                        }
                        //else
                        //{
                        //    throw new ArgumentException("SendKeys string {0} is not valid.", key_string);
                        //}

                        int repeat = 1;
                        if (repeats.Length > 0)
                            repeat = Int32.Parse(repeats.ToString());
                        if (isVkey)
                            AddVKey((int)keywords[group_string.ToString().ToUpper()], repeats.Length == 0 ? 1 : repeat);
                        else
                        {
                            if (Char.IsUpper(Char.Parse(group_string.ToString())))
                            {
                                if (!isShift)
                                    AddVKey((int)keywords["+"], true);
                                AddKey(Char.Parse(group_string.ToString()), repeats.Length == 0 ? 1 : repeat);
                                // AddKey(Char.Parse(group_string.ToString()), 1);
                                if (!isShift)
                                    AddVKey((int)keywords["+"], false);
                            }
                            else
                                AddKey(Char.Parse(group_string.ToString()), repeats.Length == 0 ? 1 : repeat);
                        }

                        i = start;
                        isRepeat = isVkey = false;
                        if (isShift)
                            AddVKey((int)keywords["+"], false);
                        if (isCtrl)
                            AddVKey((int)keywords["^"], false);
                        if (isAlt)
                            AddVKey((int)keywords["%"], false);
                        isShift = isCtrl = isAlt = false;
                        break;

                    case '+':
                        {
                            AddVKey((int)keywords["+"], true);
                            isShift = true; ;
                            break;
                        }

                    case '^':
                        {
                            AddVKey((int)keywords["^"], true);
                            isCtrl = true;
                            break;
                        }

                    case '%':
                        {
                            AddVKey((int)keywords["%"], true);
                            isAlt = true;
                            break;
                        }

                    case '~':
                        {
                            AddVKey((int)keywords["ENTER"], 1);
                            break;
                        }

                    case '(':
                        isBlock = true;
                        break;

                    case ')':
                        {
                            if (isShift)
                                AddVKey((int)keywords["+"], false);
                            if (isCtrl)
                                AddVKey((int)keywords["^"], false);
                            if (isAlt)
                                AddVKey((int)keywords["%"], false);
                            isShift = isCtrl = isAlt = isBlock = false;
                            break;
                        }

                    default:
                        {
                            if (Char.IsUpper(key_string[i]))
                            {
                                if (!isShift)
                                    AddVKey((int)keywords["+"], true);
                                AddKey(key_string[i], 1);
                                if (!isShift)
                                    AddVKey((int)keywords["+"], false);
                            }
                            else
                                AddKey(Char.Parse(key_string[i].ToString().ToUpper()), 1);

                            if (!isBlock)
                            {
                                if (isShift)
                                    AddVKey((int)keywords["+"], false);
                                if (isCtrl)
                                    AddVKey((int)keywords["^"], false);
                                if (isAlt)
                                    AddVKey((int)keywords["%"], false);
                                isShift = isCtrl = isAlt = isBlock = false;
                            }
                            break;
                        }
                }
            }

            if (isBlock)
                throw new ArgumentException("SendKeys string {0} is not valid.", key_string);

            if (isShift)
                AddVKey((int)keywords["+"], false);
            if (isCtrl)
                AddVKey((int)keywords["^"], false);
            if (isAlt)
                AddVKey((int)keywords["%"], false);

        }

        private static void SendInput()
        {
            XKeyboard.SendInput((IntPtr) 0, keys);
            keys.Clear();
        }

        #endregion	// Private Methods

        #region Public Static Methods
        public static void Flush()
        {
            Application.DoEvents();
        }

        public static void Send(string keys)
        {
            switch (Environment.OSVersion.Platform)
            {
                case PlatformID.Unix:
                    Parse(keys);
                    SendInput();
                    break;
                case PlatformID.Win32NT:
                case PlatformID.Win32S:
                case PlatformID.Win32Windows:
                    // temporary for test
                    //Parse(keys);
                    //SendInput();
                    SendKeys.SendWait(keys);
                    break;
                default:
                    break;
            }
        }

        private static object lockobj = new object();
        public static void SendWait(string keys)
        {
            lock (lockobj)
            {
                Send(keys);
            }
            Flush();
        }

        #endregion	// Public Static Methods
    }
}